﻿using System;
using System.Collections.Generic;

namespace CalculationCore.UX
{
    public class Function
    {
        public BinaryTree id;
        public Function()
        {
            this.id = new BinaryTree();
        }

        public Function(string funcname)
        {
            this.id = MakeTree(funcname);
        }

        private bool IsArithmeticOperation(char c)
        {
            if ((c == '+') || (c == '*') || (c == '/') || (c == '-') || (c == '^'))
                return true;
            else
                return false;
        }

        //OVAKO TREBA DA PISES KOMENTARE ZA FUNKCIJE :)))))
        /// <summary>
        /// Chek is strig s repesent a basic function
        /// </summary>
        /// <param name="s">s string that contain name of some function</param>
        /// <returns>true if string s is a basic function  </returns>
        private bool IsBasicFunction(string s)
        {
            switch (s)
            {
                case "cos":
                case "sin":
                case "tg":
                case "ctg":
                case "arcsin":
                case "arccos":
                case "arctg":
                case "arcctg":
                case "ln": return true;
                default:
                    return false;
            }
        }

        public string ReadNext(string s, ref int counter)
        {
            string temps = "", temps1 = "";
            int n, i;
            i = 0;
            char current = s[counter];
            while (Char.IsLetterOrDigit(current) && (counter > -1))
            {
                temps1 += current;
                counter--;
                if (counter < 0) current = '\0';
                else
                    current = s[counter];
                i++;
            }
            n = i;
            for (int j = 0; j < n; j++)
            {
                temps += temps1[i - 1];
                i--;
            }
            return temps;
        }

        private BinaryTree Make(string s, ref int counter)
        {
            BinaryTree member = null, l, r, o;
            List<BinaryTree> tempblist = new List<BinaryTree>();
            char current = s[counter];
            int priority = 0;
            string temps = "";
            while ((current != '(') && (counter > -1))
            {
                member = new BinaryTree();
                if (IsArithmeticOperation(current) && ((int)priority == 1))
                {
                    r = tempblist[0];
                    tempblist.RemoveAt(0);
                    o = tempblist[0];
                    tempblist.RemoveAt(0);
                    l = tempblist[0];
                    tempblist[0] = o;
                    o.Left = r;
                    o.Right = l;
                }

                if (current == '*' || current == '/')
                    priority = 1;
                else
                    if (current == '+' || current == '-')
                        priority = 0;

                if (current == ')')
                {
                    counter--;
                    current = s[counter];
                    member = Make(s, ref counter);
                    if (counter < 0) current = '\0';
                    else
                        current = s[counter];
                    if (Char.IsLetterOrDigit(current))
                    {
                        temps = ReadNext(s, ref counter);
                        if (counter < 0) current = '\0';
                        else
                            current = s[counter];
                        BinaryTree temp = new BinaryTree("o");
                        temp.Right = member;
                        temp.Left = new BinaryTree(temps.ToString());
                        member = temp;
                    }
                }

                else
                {
                    if (Char.IsLetterOrDigit(current))
                    {
                        temps = ReadNext(s, ref counter);
                        if (counter < 0) current = '\0';
                        else
                            current = s[counter];
                    }
                    else
                    {
                        temps = "";
                        temps += current;
                        counter--;
                        if (counter < 0) current = '\0';
                        else
                            current = s[counter];
                    }
                    member = new BinaryTree(temps);
                }

                tempblist.Insert(0, member);
            }

            if (current == '(')
            {
                counter--;
                if (counter < 0) current = '\0';
                else current = s[counter];
            }
            return FromListToTree(tempblist);
        }

        public BinaryTree FromListToTree(List<BinaryTree> blist)
        {
            BinaryTree r, o, l;
            int i;
            for (i = 0; i < blist.Count - 1; i += 2)
            {
                r = blist[i];
                o = blist[i + 1];
                l = blist[i + 2];
                blist[i + 2] = o;
                o.Left = r;
                o.Right = l;
            }
            return blist[i];
        }

        public BinaryTree MakeTree(string s)
        {
            int counter = s.Length - 1;
            return Make(s, ref counter);
        }

        private BinaryTree DeriveTree(BinaryTree tree)
        {
            //if (tree==null) pukni;
            BinaryTree temptree = new BinaryTree(tree.inf);
            switch (tree.inf)
            {
                case "+":
                case "*":
                case "/":
                case "-":
                    {
                        temptree.Left = DeriveTree(tree.Left);
                        temptree.Right = DeriveTree(tree.Right);
                        return temptree;
                    }
                case "^"://za sada radi samo ako je stepen konstanta, kada dodamo i eksp fjumafju standardne fje radice i za nju
                    {
                        float tempf;
                        BinaryTree temp1 = new BinaryTree("*");
                        tempf = float.Parse(tree.Right.inf);
                        temp1.Right = tree.Left;
                        temp1.Left = new BinaryTree((tempf).ToString());
                        temptree.Right = new BinaryTree((tempf - 1).ToString());
                        temptree.Left = temp1;
                        temp1 = new BinaryTree("*");
                        temp1.Left = temptree;
                        temp1.Right = DeriveTree(tree.Left);
                        return temp1;
                    }
                case "o":
                    {
                        BinaryTree temp1 = new BinaryTree("*");
                        temptree.Left = DeriveTree(tree.Left);
                        temptree.Right = tree.Right;//ovde bi trebalo da bude nesto da iskopaira stablo tree.Right 
                        //da se ne bi fja menjala kada nesto promenimo u njenoj izvodnoj fji
                        //treba naci sva ovakva mesta i to promeniti
                        temp1.Left = temptree;
                        temp1.Right = DeriveTree(tree.Right);
                        return temp1;
                    }
                case "cos":
                    {
                        return MakeTree("(0-1)*sin");//imamo problema sa unarnim operatorima za sad

                    }
                case "sin":
                    return new BinaryTree("cos");
                case "tg":
                    {
                        return MakeTree("1/cos(x)^2");//ne valja sto sam ovde fiksirao da e x ime nepoznate ali svejedno ceo kod 
                        //ionako sve sto nije ni konstanta ni fja kao istu nepoznatu
                    }
                case "ctg":
                    {
                        return new BinaryTree("under construction...");
                    }
                case "arcsin":
                    {
                        return new BinaryTree("under construction...");
                    }
                case "arccos":
                    {
                        return new BinaryTree("under construction...");
                    }
                case "arctg":
                    {
                        return new BinaryTree("under construction...");
                    }
                case "arcctg":
                    {
                        return new BinaryTree("under construction...");
                    }
                case "ln":
                    {
                        return new BinaryTree("under construction...");
                    }
                default://ako nije nista od ovoga do sada onda je ili promenljiva ili neka konstanta
                    {
                        float tempf;
                        if (float.TryParse(tree.inf, out tempf))
                            return new BinaryTree("0");//verovatno je i null moglo da se vrati, ali za sad neka stoji ovako
                        return new BinaryTree("1");
                    }
            }
        }

        public double Value(double x)
        {
            return ValueTree(this.id, x);
        }

        public Function Derive()
        {
            Function tempfunction = new Function();
            tempfunction.id = DeriveTree(this.id);
            return tempfunction;
        }

        private double ValueTree(BinaryTree tree, double x)
        {
            //if (tree==null) pukni;

            switch (tree.inf)
            {
                case "+": return ValueTree(tree.Left, x) + ValueTree(tree.Right, x);
                case "*": return ValueTree(tree.Left, x) * ValueTree(tree.Right, x);
                case "/": return ValueTree(tree.Left, x) / ValueTree(tree.Right, x);
                case "-": return ValueTree(tree.Left, x) - ValueTree(tree.Right, x);
                case "^": return Math.Pow(ValueTree(tree.Left, x), ValueTree(tree.Right, x));
                case "o": return ValueTree(tree.Left, ValueTree(tree.Right, x));
                case "cos":
                    return Math.Cos(x);
                case "sin":
                    return Math.Sin(x);
                case "tg":
                    return Math.Tan(x);
                case "ctg":
                    return 1 / Math.Tan(x);
                case "arcsin":
                    return Math.Asin(x);
                case "arccos":
                    return Math.Acos(x);
                case "arctg":
                    return Math.Atan(x);
                case "arcctg":
                    return x;
                case "ln":
                    return Math.Log(x);
                default:
                    {
                        float tempf;
                        if (float.TryParse(tree.inf, out tempf))
                            return tempf;
                        return x;
                    }
            }
        }

        public double derive(double x)
        {
            switch (id.inf)
            {
                case "":
                    return 0;
                case "ln":
                    return 1 / x;
                case "sin":
                    return Math.Cos(x);
                case "cos":
                    return -Math.Sin(x);
                case "tg":
                    return 1 / (Math.Cos(x) * Math.Cos(x));
                case "ctg":
                    return -1 / (Math.Cos(x) * Math.Cos(x));
                case "arcsin":
                    return 1 / Math.Sqrt(1 - x * x);
                case "arccos":
                    return -1 / Math.Sqrt(1 - x * x);
                case "arctg":
                    return 1 / (1 + x * x);
                case "arcctg":
                    return -1 / (1 + x * x);
                default:
                    //ovde ide slozen izvod
                    return x;
            }
        }

        public double Integral(double x)
        {
            switch (id.inf)
            {
                case "":
                    return x;
                case "sin":
                    return -Math.Cos(x);
                case "cos":
                    return Math.Sin(x);
                case "1/cos^2":
                    return Math.Tan(x);
                case "-1/sin^2":
                    return -1 / Math.Tan(x);
                case "1/sin":
                    return Math.Log(Math.Abs(Math.Tan(x / 2)));
                case "1/cos":
                    return Math.Log(Math.Abs(Math.Tan(x / 2 + Math.PI / 4)));
                default:
                    //ovde ide slozen integral
                    return x;
            }
        }
        public double Integral(double a, double b)
        {
            double s = 0, d = (b - a) / 50;
            for (int i = 0; i <= 50; i++)
            {
                s += this.Value(a + (i + 0.5) * d);
            }
            return s * d;
        }

        override public string ToString()
        {
            return this.id.ToString();
        }
    }
}
